
local ipairs   = ipairs
local pairs    = pairs
local assert   = assert
local error    = error
local next     = next
local type     = type
local loadfile = loadfile
local tonumber = tonumber
local os       = os
local string   = string

local save = require "core.save"

local DEFINE_ROW = 2
local CODE_NAME_ROW = 3
local DATA_START_ROW = 4
local START_COL = 2

local S_BOOL,   T_BOOL   = "bool",   1
local S_INT,    T_INT    = "int",    2
local S_FLOAT,  T_FLOAT  = "float",  3
local S_STRING, T_STRING = "string", 4

local STR_T_2_CODE_T = {
	[S_BOOL]   = T_BOOL,
	[S_INT]    = T_INT,
	[S_FLOAT]  = T_FLOAT,
	[S_STRING] = T_STRING,
}

local function errf(...)
	error(string.format(...), 2)
end

local function _p_define_row(one, sheet_name, define_row)
	local defines = {}
	local i = START_COL
	while true do
		local v = define_row[i]
		if not v then
			break
		end

		i = i + 1

		local is_list = false
		local base = STR_T_2_CODE_T[v]
		if not base then
			local s = v:match("list<(%a+)>")
			base = STR_T_2_CODE_T[s]
			is_list = true
		end
		if not base then
			error(string.format("xlsx_file:%s sheet:%s invalid define:%s", one.xlsx_file, sheet_name, v))
		end
		defines[#defines + 1] = {
			base = base,
			is_list = is_list,
		}
		::continue::
	end
	return defines
end

local function _p_code_name(_, _, code_name_row)
	local names = {}
	local i = START_COL
	while true do
		local v = code_name_row[i]
		if not v then
			break
		end

		names[#names + 1] = v
		i = i + 1
	end
	return names
end

local function _check_field_type(v, t)
	if v == nil then
		return true
	end
	if t == T_BOOL then
		return type(v) == "boolean"
	elseif t == T_INT then
		return math.type(v) == "integer"
	elseif t == T_FLOAT then
		return math.type(v) == "float"
	elseif t == T_STRING then
		return type(v) == "string"
	else
		error(t)
	end
end

local function _parse_field_value(v, t)
	if v == nil then
		return nil
	end
	if t == T_BOOL then
		return v == "TRUE" or v == "true"
	elseif t == T_INT then
		return math.tointeger(v)
	elseif t == T_FLOAT then
		return tonumber(v)
	elseif t == T_STRING then
		return v
	else
		error(t)
	end
end

-- list 以 | 分割
local function _parse_list(v, t)
	if v == nil then
		return nil
	end

	-- print("parse list", v)
	local ret = {}
	for s in string.gmatch(v, "([^|]+)") do
		local rv = _parse_field_value(s, t)
		if rv == nil then
			return false
		end
		ret[#ret + 1] = rv
	end
	return ret
end

local function _p_data(one, sheet_name, row, data_row, defines, names)
	local data = {}
	print("parse data start", one.xlsx_file, sheet_name, save.tostring(defines))
	for i = 1, #defines do
		local d, n, v = defines[i], names[i], data_row[i + START_COL - 1]
		if not d.is_list then
			if not _check_field_type(v, d.base) then
				errf(errf("xlsx_file:%s sheet:%s row:%d n:%s type mismatch", one.xlsx_file, sheet_name, row, n))
			end
		else
			v = _parse_list(v, d.base)
			if v == false then
				errf(errf("xlsx_file:%s sheet:%s row:%d n:%s list field mismatch", one.xlsx_file, sheet_name, row, n))
			end
		end

		assert(data[n] == nil)
		print("parse data", one.xlsx_file, n, v)
		data[n] = v
	end
	return data
end

local function _convert_sheet(one, sheet_name, sheet)
	local define_row = sheet[DEFINE_ROW]
	if not define_row then
		errf("xlsx_file:%s sheet:%s no define row", one.xlsx_file, sheet_name)
	end
	local code_name_row = sheet[CODE_NAME_ROW]
	if not code_name_row then
		errf("xlsx_file:%s sheet:%s no code name row", one.xlsx_file, sheet_name)
	end
	if not sheet[DATA_START_ROW] then
		errf("xlsx_file:%s sheet:%s no data row", one.xlsx_file, sheet_name)
	end

	local defines = _p_define_row(one, sheet_name, define_row)
	local names = _p_code_name(one, sheet_name, code_name_row)
	if #defines == 0 or #defines ~= #names then
		errf("xlsx_file:%s sheet:%s defines:%d names:%s mismatch", one.xlsx_file, sheet_name, #defines, #names)
	end

	local sheet_data = {}
	local i = DATA_START_ROW
	while true do
		local data_row = sheet[i]
		if not data_row or not next(data_row) then
			break
		end
		local row_data = _p_data(one, sheet_name, i, data_row, defines, names)
		sheet_data[#sheet_data + 1] = row_data
		i = i + 1
	end
	return sheet_data
end

local function _convert(one, raw_file)
	print("convert raw_file", raw_file)
	local raw_table = assert(loadfile(raw_file, "t", {}), raw_file)()
	-- print("convert raw_table", raw_file, save.tostring(raw_table))
	local result_table = {}
	for sheet_name, sheet in pairs(raw_table) do
		result_table[sheet_name] = _convert_sheet(one, sheet_name, sheet)
	end
	return result_table
end

local function _parse_one(cfg, one)
	local cmd = string.format("%s %s %s", cfg.exec_file, one.xlsx_file, one.raw_file)
	local ok, flag, code = os.execute(cmd)
	if not ok then
		errf("fail to exec cmd:" .. cmd)
	end
	if flag ~= "exit" then
		errf("not exit cmd:" .. cmd)
	end
	if code ~= 0 then
		errf("not succeed cmd:" .. cmd)
	end
	print(cmd, ok, flag, code)
	result_table = _convert(one, one.raw_file)
	save.save(result_table, one.result_file)
end


-- file path should be utf-8.
-- cfg = {
--	exec_file=,
--   list = {
--       xlsx 文件，   原始的 lua 文件，  最终的 lua 文件
--     {xlsx_file=,  raw_file =,       result_file=},
--   },
-- }
local function _parse(cfg)
	for _, v in ipairs(cfg.list) do
		_parse_one(cfg, v)
	end
end

return _parse